package com.wms.common;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import com.wms.domain.BasePageDomain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

import static org.springframework.util.ObjectUtils.isEmpty;

@Component
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class , Integer.class}) })
public class PageInterceptor implements Interceptor{

    private static final Logger logger = LoggerFactory.getLogger(PageInterceptor.class);

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private static final String PAGE_KEY = "page";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获得拦截的对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 待执行的sql的包装对象
        BoundSql boundSql = statementHandler.getBoundSql();
        // 判断是否是查询语句
        if (isSelect(boundSql.getSql())) {
            // 获得参数集合
            Object params = boundSql.getParameterObject();

            if (params instanceof Map) {
                // 请求为多个参数，参数采用Map封装
                return complexParamsHandler(invocation, boundSql, (Map<?, ?>) params);
            } else if (params instanceof BasePageDomain) {
                // 单个参数且为Page，则表示该操作需要进行分页处理
                return simpleParamHandler(invocation, boundSql, (BasePageDomain) params);
            }
        }
        return invocation.proceed();
    }

    private Object complexParamsHandler(Invocation invocation, BoundSql boundSql, Map<?, ?> params) throws Throwable {
        //判断参数中是否指定分页
        if (containsPage(params)) {
            return pageHandlerExecutor(invocation, boundSql, (BasePageDomain) params.get("page"));
        } else {
            return invocation.proceed();
        }
    }

    private boolean containsPage(Map<?, ?> params) {
        if(params==null){
            return false;
        }else if(!params.containsKey(PAGE_KEY)){
            return false;
        }
        Object page = params.get(PAGE_KEY);
        if(page==null){
            return false;
        }else {
            return page instanceof BasePageDomain;
        }
    }

    private boolean isSelect(String sql) {
        return StringUtils.hasText(sql) && sql.toUpperCase().trim().startsWith("SELECT");
    }

    private Object simpleParamHandler(Invocation invocation, BoundSql boundSql, BasePageDomain page) throws Throwable {
        return pageHandlerExecutor(invocation, boundSql, page);
    }

    private Object pageHandlerExecutor(Invocation invocation, BoundSql boundSql, BasePageDomain page) throws Throwable {
        // 获得数据库连接
        Connection connection = (Connection) invocation.getArgs()[0];
        // 使用Mybatis提供的MetaObject，该对象主要用于获取包装对象的属性值
        MetaObject statementHandler = MetaObject.forObject(invocation.getTarget(), DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);

        // 获取该sql执行的结果集总数
        if(page.getStartPosition() != null && page.getMaxResult() != null){
            int maxSize = getTotalSize(connection, (MappedStatement) statementHandler.getValue("delegate.mappedStatement"),
                    boundSql);
            page.setBaseCount((long) maxSize);
        }

        //排序
        if (page.getSort() !=null && page.getSortType() != null){
            String wrapperSql = getOrderSql(boundSql.getSql(), page);
            logger.info("alter order by sql "+wrapperSql);

            MetaObject boundSqlMeta = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
                    DEFAULT_REFLECTOR_FACTORY);
            // 修改boundSql的sql
            boundSqlMeta.setValue("sql", wrapperSql);
            return invocation.proceed();
        }
        //合计
        Object baseSum = getBaseSum(connection, (MappedStatement) statementHandler.getValue("delegate.mappedStatement"),
                boundSql);
        if (baseSum != null) {
            page.setBaseSum(baseSum);
        }


        //limit
        if(page.getStartPosition() != null && page.getMaxResult() != null){
            String wrapperSql = getPageSql(boundSql.getSql(), page);
            logger.info(wrapperSql);

            MetaObject boundSqlMeta = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
                    DEFAULT_REFLECTOR_FACTORY);
            // 修改boundSql的sql
            boundSqlMeta.setValue("sql", wrapperSql);
            return invocation.proceed();
        }
        return invocation.proceed();
    }

    private int getTotalSize(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) {
        String countSql = getCountSql(boundSql.getSql());
        PreparedStatement countStmt;
        ResultSet rs;
        List<AutoCloseable> closeableList = new ArrayList<>();

        try {
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBoundSql, boundSql.getParameterObject());
            rs = countStmt.executeQuery();

            if (rs.next()) {
                return rs.getInt(1);
            }
            closeableList.add(countStmt);
            closeableList.add(rs);
        } catch (SQLException e) {
            logger.error("append an exception[{}] when execute sql[{}] with {}", e, countSql,
                    boundSql.getParameterObject());
        } finally {
            for (AutoCloseable closeable : closeableList) {
                try {
                    if (closeable != null) {
                        closeable.close();
                    }
                } catch (Exception e) {
                    logger.error("append an exception[{}] when close resource[{}] ", e, closeable);
                }
            }
        }
        return 0;
    }
    private Object getBaseSum(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) {
        String sql = boundSql.getSql();
        String countSql;
        List<String> key = parseKey(sql);
        if(!key.isEmpty()){
            String sumSql = createSql(key);
            countSql =  sumSql + "(" + sql + ") as sum";
            logger.info("alter sum sql"+sumSql);
        }else{
            return null;
        }
        if(isEmpty(countSql)){
            return null;
        }
        PreparedStatement countStmt;
        ResultSet rs;
        List<AutoCloseable> closeableList = new ArrayList<>();

        try {
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBoundSql, boundSql.getParameterObject());
            rs = countStmt.executeQuery();
            Map<String, BigDecimal> result = new HashMap<String, BigDecimal>();
            while (rs.next()) {
                for (String columnLabel : key) {
                    if(rs.getObject(columnLabel) != null){
                        result.put(columnLabel, new BigDecimal(String.valueOf(rs.getObject(columnLabel))));
                    }

                }
            }
            closeableList.add(countStmt);
            closeableList.add(rs);
            return result;
        } catch (SQLException e) {
            logger.error("append an exception[{}] when execute sql[{}] with {}", e, countSql,
                    boundSql.getParameterObject());
        } finally {
            for (AutoCloseable closeable : closeableList) {
                try {
                    if (closeable != null) {
                        closeable.close();
                    }
                } catch (Exception e) {
                    logger.error("append an exception[{}] when close resource[{}] ", e, closeable);
                }
            }
        }
        return null;
    }

    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }

    public String getCountSql(String sql) {
        //不排序
        return "select count(0) from (" + sql + ") as total";
    }
    public String getSumSql(String sql) {
        List<String> key = parseKey(sql);
        if(!key.isEmpty()){
            String sumSql = createSql(key);
            return sumSql + "(" + sql + ") as sum";
        }else{
            return null;
        }
    }
    public List<String> parseKey(String sql){
        List<String> list = new ArrayList<String>();
        int begin = sql.indexOf("SELECT");
        int end = sql.indexOf("FROM");
        String keys = sql.substring(begin+6,end);
        if(isEmpty(keys)){
            return null;
        }
        StringTokenizer result = new StringTokenizer(keys,",");
        if(isEmpty(result)){
            return null;
        }
        while(result.hasMoreTokens()){ // 对 result 遍历并打印子字符串；
            String key = result.nextToken();
            key = key.trim();
            if(key.indexOf(".") == 1){
                int index = key.indexOf(".");
                key = key.substring(index+1);
            }
            if(Config.table_key.containsKey(key)){
                list.add(key);
            }
        }
        return list;
    }
    public String createSql(List<String> keys){
        StringBuilder newSql = new StringBuilder("SELECT ");
        for (String key:keys){
            newSql.append(" SUM(").append(key).append(") as ").append(key).append(",");
        }
        newSql.deleteCharAt(newSql.length() - 1);
        newSql.append(" FROM ");
        return newSql.toString();
    }



    public String getOrderSql(String sql, BasePageDomain page){
        return  sql + " ORDER BY " + page.getSort() + " "+ page.getSortType();
    }
    public String getPageSql(String sql, BasePageDomain page) {
        return sql+" limit "+page.getStartPosition()+", "+page.getMaxResult();
    }
}
